import imp
from grammar import *
import sys
import importlib
import macros

import collections
import traceback

class Context(object):
    globalVars = {}
    stack = []
    env = {}
    
    def __init__(self, *args):
        if len(args) == 3:
            self.env, self.scope, self.stack = args
        else:
            ctx = args[0]
            self.scope = list(ctx.scope)
        #endif
        
    def getVar(self, name, index=None):
        """
        Search through the stack in reverse order for variable.
        """
        for stack in self.stack[::-1]:
            if name in stack: 
                var = stack[name]
                if index == None:
                    return var
                elif isinstance(index, Variable):
                    return var[self.getVar(index.name, index.index)]
                else:
                    return var[index]
        return None
                
    def setVar(self, name, value):
        self.stack[-1][name] = value
        
    def getGlobalVar(self, name):
        return self.globalVars[name]

    def setGlobalVar(self, name, value):
        self.globalVars[name] = value

def getMacroInstance(ctx, macro):
    doctype = ctx.env["doctype"]
    cls = Macro.find(doctype, macro.name)
    
    # need to find any macro-arguments and execute them
    args = []
    for arg in macro.parameters.args:
        if isinstance(arg, MacroCall):
            instance = getMacroInstance(ctx, arg)
            eb = ExecutionBlock(instance, arg.block, arg.delayed)
            args.append(eb.execute(ctx))
        elif isinstance(arg, Variable):
            args.append(ctx.getVar(arg.name, arg.index))
        else:
            args.append(arg)
            
    kwargs = {}
    for key, value in macro.parameters.kwargs.items():
        if isinstance(value, MacroCall):
            instance = getMacroInstance(ctx, value)
            eb = ExecutionBlock(instance, value.block, value.delayed)
            kwargs[key] = eb.execute(ctx)
        elif isinstance(value, Variable):
            kwargs[key] = ctx.getVar(value.name, value.index)
        else:
            kwargs[key] = value
    
    if cls != None:
        try:
            # call the constructor of the macro type
            newCtx = Context(ctx)
            #newCtx.stack.append({})
            result = cls(newCtx, *args, **kwargs)                 
            #newCtx.stack.pop()
            return result
        except TypeError as e:
            print "## macro: ", macro.name
            print "e = ", e
            traceback.print_stack()
            raise MacroIncorrectParameters(macro.name, macro.parameters)
        #endtry
    else:
        raise MacroNotFound(macro.name)
    #endif
    
class DelayedMacro(object):
    def __call__(self, content):
        return content
        
class Block(object):
    def __init__(self, contents):
        self.contents = contents
        
    def __iter__(self):
        return iter(self.contents)
        
    def __len__(self):
        return len(self.contents)
        
    def __getitem__(self, index):
        return self.contents[index]
        
    def __repr__(self):
        return "".join([str(e) for e in self.contents])
        
class ExecutionBlock(object):
    def __init__(self, instance, block, delayed=False):
        self.instance = instance
        self.block = block
        self.delayed = delayed
        
    def expandBlock(self, ctx, block):
        """
        Go through and associate macros with their content. Because macros
        can be embedded, we need to do this recursively.
        """
        if isinstance(block, LiteralBlock): return str(block)
        parts = []
        for item in block:
            if isinstance(item, MacroCall):
                macro = item
                instance = getMacroInstance(ctx, macro)
                eb = ExecutionBlock(instance, item.block, item.delayed)
                value = eb.execute(ctx)
            elif isinstance(item, UnprocessedBlock): 
                value = self.expandBlock(ctx, item)
            elif isinstance(item, Variable): 
                value = ctx.getVar(item.name, item.index)
            else: 
                value = item

            if value != None: 
                if isinstance(value, list): parts.extend(value)
                else: parts.append(value)
        #endfor

        return Block(parts)
        
    def execute(self, ctx):
        """
        Creates a MacroExpression that contains an instantiation of the macro
        with its contents. The contents may contain other macros, so expansion
        is recursive in nature.
        """        
        if self.delayed:
            content = ExecutionBlock(DelayedMacro(), self.block)
        else:
            newCtx = Context(ctx)
            newCtx.scope.append(self.instance)
            newCtx.stack.append({})            
            content = self.expandBlock(newCtx, self.block)            
        
        # return resulting expansion
        if callable(self.instance):
            result = self.instance(content)
        else:
            result = None
            
        if not self.delayed: newCtx.stack.pop()
        return result

class PlainText(object):
    def __init__(self, value):
        self.value = value
        
    def __str__(self):
        return str(self.value)

class MacroType(type):
    registry = {}
    
    def __init__(self, name, bases, dct):
        super(MacroType, self).__init__(name, bases, dct)
        doctype = getattr(self, "doctype", "all")
        id = getattr(self, "name", name)
        MacroType.register(doctype, id, self)
        
    @classmethod
    def register(cls, doctype, name, macro):
        if not id in MacroType.registry: MacroType.registry[name] = {}
        MacroType.registry[name][doctype] = macro
        
    @classmethod
    def find(cls, doctype, name):
        types = MacroType.registry[name]
        if not doctype in types:
            try:     
                return types["all"]
            except:
                raise MacroNotFound(name)
        return types[doctype]

class Macro(object):
    __metaclass__ = MacroType
    
    def __init__(self):
        super(Macro, self).__init__()
        
    # TODO: it's not clear if flatten is still required -- review the cases
    # it's used
    @staticmethod
    def flatten(content):
        # if it's not iterable, just return it
        if not isinstance(content, collections.Iterable):
            return content
            
        # if it's a Block, then we can't flatten it
        if isinstance(content, Block) or isinstance(content, LiteralBlock):
            return content        
            
        # if it's a string, we also can't faltten it
        if isinstance(content, str): 
            return content
        
        # otherwise, merge all strings together
        result = []
        pos = 0        
        while pos < len(content):
            if isinstance(content[pos], str):
                merge = []                
                while pos < len(content) and isinstance(content[pos], str):
                    merge.append(content[pos])
                    pos += 1
                result.append("".join(merge))
            else:
                result.append(content[pos])
                pos += 1
        #endwhile
        
        return result

class Import(Macro):
    name = "import"

    def __init__(self, ctx, name):
        super(Import, self).__init__()
        parts = name.split(".")
        packageName = ".".join(parts[:-1])
        moduleName = parts[-1]
        if packageName != "":
            importlib.import_module(".%s" % moduleName, packageName)
        else:
            importlib.import_module("pod.macros.%s" % name)
        #endif
        
    def __call__(self, content):
        pass
        
class AddPath(Macro):
    name = "addpath"
    
    def __init__(self, ctx, path):
        sys.path.append(path)
                
class Default(Macro):
    name = "default"
    
    def __init__(self, ctx, **kwargs):
        super(Default, self).__init__()
        for key, value in kwargs.items():
            ctx.env["default:%s" % key] = value

class Include(Macro):
    name = "include"

    def __init__(self, ctx, filename):
        super(Include, self).__init__()
        self.content = parseFile(filename, env)

    def __call__(self, content):
        return self.content

class AnonymousBlock(Macro):
    name = "anon"

    def __init__(self, ctx):
        super(AnonymousBlock, self).__init__()

    def __call__(self, content):
        return content
        
class UserMacroFactory(object):
    def __init__(self, env, *args, **kwargs):
        super(UserMacroFactory, self).__init__()
        self.env = env
        self.vars = args
        self.optionalVars = kwargs
        self.block = None
        
    def __call__(self, ctx, *args, **kwargs):
        vars = {}
        for var, value in zip(self.vars, args):
            vars[var] = value

        # set defaults
        for var, default in self.optionalVars.items():
            vars[var] = default
            
        for var, value in kwargs.items():
            vars[var] = value
                    
        return UserMacro(self.env, self.block, vars)
        
class UserMacro(object):
    def __init__(self, ctx, block, vars):
        super(UserMacro, self).__init__()
        self.ctx = ctx
        self.vars = vars
        self.block = block
        
    def __call__(self, content):
        for key, value in self.vars.items():
            self.ctx.env["var:%s" % key] = value
        
        return self.block.execute(self.ctx)

class CreateMacro(Macro):
    name = "macro"
    
    def __init__(self, ctx, *args, **kwargs):
        super(CreateMacro, self).__init__()
        self.doctype = getattr(self, "doctype", "all")
        self.id = args[0]
        Macro.register(self.doctype, self.id, UserMacroFactory(ctx, *args[1:], **kwargs))    
        
    def __call__(self, content):
        cls = Macro.find(self.doctype, self.id)
        cls.block = content
        
class MakeDict(Macro):
    name = "dict"
    
    def __init__(self, ctx, **kwargs):
        super(MakeDict, self).__init__()        
        self.value = DictType(kwargs)
    
    def __call__(self, content):
        return self.value

class MakeList(Macro):
    name = "list"
    
    def __init__(self, ctx, *args):
        super(MakeList, self).__init__()
        self.value = ListType(*args)
        
    def __call__(self, content):
        return self.value
